#!/usr/bin/env python
# coding: utf-8

# 首先读取单个数据，查看文件结构

# In[1]:



# In[1]:


import pandas as pd
import numpy as np


# In[2]:


data = pd.read_csv('./data/hy_round1_train_20200102/1.csv')
print(data)


# 每个文件包含7列，分别是ID，x，y,速度，方向,时间以及类型。
# 
# 
# 其中x，y，速度，方向都是数值类型，我们可以对这些数值进行一个统计
# 用以观察这一系列数据的范围、方差等等。

# In[3]:


print(data[["x", "y", "速度", '方向']].describe())


# 接下来，需要对数据进行特征的提取。在此我们主要对x，y，速度，方向四个属性的统计特征进行提取

# In[4]:


#这里采用x，y，速度，方向四个属性中的最大值，最小值，平均值以及标准差作为每个渔船的特征
def get_feature(df, train_mode=True):
    """
    test_mode: 用于区分训练数据和测试数据，训练数据存在label而测试数据不存在label
    """
    df = df.iloc[::-1]

    if train_mode:
        df['type'] = df['type'].map({'拖网': 0, '围网': 1, '刺网': 2})# 将label由str类型转换为int类型
        label = np.array(df['type'].iloc[0])
        df = df.drop(['type'], axis=1)
    else:
        label = None
    features = np.array([df['x'].std(), df['x'].mean(), df['x'].max(), df['x'].min(),
                df['y'].std(), df['y'].mean(), df['y'].max(), df['y'].min(),
                df['速度'].mean(), df['速度'].std(), df['速度'].max(), df['速度'].min(),
                df['方向'].mean(), df['方向'].std(), df['方向'].max(), df['方向'].min(),
                ])
    return features, label


# 查看单个渔船的特征，以及标签属性

# In[5]:


get_feature(data)


# 接下来读取全部的文件，并提取其中的特征以及标签，然后将这些数据装到一个list中

# In[6]:


import os
def load_data():
    path = './data/hy_round1_train_20200102'
    train_file = os.listdir(path)
    X = []
    Y = []
    for i, each in enumerate(train_file):
        if not i % 1000:  #每读1000个文件输出一次
            print(i)
        each_path = os.path.join(path, each)
        df = pd.read_csv(each_path)
        x, y = get_feature(df)
        X.append(x)
        Y.append(y)
    X = np.array(X)
    Y = np.array(Y)
    return X, Y


# In[7]:




# 全部一次读取大概需要花1分钟（由于硬件原因，每台电脑花费的时间不尽相同）
# 
# 每次加载数据都需要不少的时间得到两个numpy数组，而且每次加载都需要重复的计算特征，同时我们需要的也仅是特征属性，其他内容不需要被读取。
# 
# 因此为了快速加载数据，我们可以把加载好的数据存为.npy格式。
# 
# .npy文件是numpy专用的二进制文件,能够快速的读取。

# In[8]:

X,Y = load_data()
np.save("./npy/x_array.npy", X)
np.save("./npy/y_array.npy", Y)


# In[9]:


# 通过.npy文件读取只需要用不到1秒的时间
# 
# 基本数据读取已经完成，接下来就是训练过程
# 
# 首先需要把数据分为训练集与测试两个部分，一般采用8:2或7:3比例

# 使用sklearn决策树对部分数据进行分类
# 
# （在深度学习中跑一次模型可能会花很长的时间，这时候先使用部分数据既可以方便程序的debug，也能对模型运行时间有个大致的概念）

# In[10]:


from sklearn import tree
from sklearn.metrics import f1_score, accuracy_score, plot_confusion_matrix
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X[:3000], Y[:3000], test_size=0.2, random_state=0)
clf=tree.DecisionTreeClassifier(random_state=2021)
clf=clf.fit(X_train,y_train)


# 对模型进行评价，在测试集对模型进行测试，计算准确率，f1_score

# In[11]:


def eval(clf,X_train, X_test, y_train, y_test):
    predicted = clf.predict(X_train)  # 模型预测
    accuracy = accuracy_score(y_train, predicted)
    print("训练集准确率", accuracy)
    f1 = f1_score(y_train, predicted,average='macro')
    print("训练集f1_score", f1)
    
    predicted = clf.predict(X_test)  
    accuracy = accuracy_score(y_test, predicted)
    print("测试集准确率", accuracy)
    f1 = f1_score(y_test, predicted,average='macro')
    print("测试集f1_score", f1)
    
    plot_confusion_matrix(clf, X_test, y_test,cmap="GnBu") 


# In[12]:


eval(clf,X_train, X_test, y_train, y_test)


# 提高准确率的方法有多种，最直接的方式就是加大数据量。刚刚只用了部分数据进行训练，现在使用全部数据构建模型

# In[13]:


X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=0)
clf=tree.DecisionTreeClassifier(random_state=2021)
clf.fit(X_train,y_train)
eval(clf,X_train, X_test, y_train, y_test)


# 决策树可视化，在本地生成pdf文件（需要安装graphviz）

# In[14]:


from sklearn.tree import DecisionTreeClassifier, export_graphviz
import graphviz
dot_data = tree.export_graphviz(clf, out_file=None) 
graph = graphviz.Source(dot_data) 
graph.render("demo") 


# 参数调优
# 
# DecisionTreeClassifier 参数如下
# 
# DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=None,
#             max_features=None, max_leaf_nodes=None,
#             min_impurity_decrease=0.0, min_impurity_split=None,
#             min_samples_leaf=1, min_samples_split=2,
#             min_weight_fraction_leaf=0.0, presort=False, random_state=None,
#             splitter='best')
# 
# - class_weight：类别权重，默认为None。可选值还有：dict,balanced。dict：指定样本各类别的权重，权重大的类别在决策树构造的时候会进行偏倚；balanced：算法自己计算权重，样本量少的类别所对应的样本权重会更高。
# - criterion：在基于特征划分数据集合时，选择特征的标准。默认是gini,也可以是entropy。
# - max_depth：决策树的最大深度，我们可以控制决策树的深度来防止决策树过拟合
# - max_features：在划分数据集时考虑的最多的特征值数量。为int或float类型。其中int值是每次split时最大特征数；float值是百分数，即特征数=max_features*n_features
# - max_leaf_nodes：最大叶子节点数。int类型，默认为None。默认情况下是不设置最大叶子节点数，特征不多时，不用设置。特征多时，可以通过设置最大叶子节点数，防止过拟合。
# - min_impurity_decrease：节点划分最小不纯度。float类型，默认值为0。节点的不纯度必须大于这个阈值，否则该节点不再生成子节点。通过设置，可以限制决策树的增长。
# - min_impurity_split：信息增益的阀值。信息增益必须大于这个阀值，否则不分裂
# - min_samples_leaf：叶子结点需要的最小样本数。如果其叶子结点数小于这个阈值，则会和兄弟节点一起被剪枝。min_samples_leaf的取值可以是int或float类型
# - int类型：最小样本数量
# - float类型：表示一个百分比。即：最小样本数=min_samples_leaf * 样本数量n，并向上取整。
# - min_samples_split：当节点的样本数少于min_samples_split时，不再继续分裂。默认值为2.
# - min_weight_fraction_leaf：默认为0.0
# - presort：bool类型，默认为False。表示在拟合前，是否对数据进行排序来加快树的构建。当数据集较小时，使用presort=true会加快分类器构建速度。当数据集较为庞大时，presort=true又会使得树的构建十分缓慢
# - random_state
# - splitter：在构造树时，选择属性特征的原则，可以是best或random。默认是best,best代表在所有的特征中选择最好的，random代表在部分特征中选择最好的。
# 
# 版权声明：本文为CSDN博主「Andrewings」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。
# 原文链接：https://blog.csdn.net/shichensuyu/article/details/96478729

# In[15]:


from sklearn.model_selection import GridSearchCV
params = {'max_depth':range(5,20),'criterion':np.array(['entropy','gini']),"class_weight":['balanced'],"random_state":[2021]}
clf = GridSearchCV(estimator=tree.DecisionTreeClassifier(), param_grid=params, cv=8, n_jobs=5, scoring="f1_macro")
clf.fit(X_train,y_train)
print("Best set score:{:.2f}".format(clf.best_score_))
print("Best parameters:{}".format(clf.best_params_))
print("Test set score:{:.2f}".format(clf.score(X_test, y_test)))
eval(clf,X_train, X_test, y_train, y_test)


# 使用更强的分类器

# In[16]:


from sklearn.ensemble import RandomForestClassifier
params = {'n_estimators':[100,200,500],'max_depth':range(15,20),
          'criterion':['entropy','gini'],"class_weight":['balanced'],"random_state":[2021],}
clf = GridSearchCV(estimator=RandomForestClassifier(), param_grid=params, cv=8, n_jobs=5, scoring="f1_macro")
clf.fit(X_train,y_train)
print("Best set score:{:.2f}".format(clf.best_score_))
print("Best parameters:{}".format(clf.best_params_))
print("Test set score:{:.2f}".format(clf.score(X_test, y_test)))
eval(clf,X_train, X_test, y_train, y_test)


# 特征选取对分类的影响

# In[17]:


def get_feature2(df, train_mode=True):
    df = df.iloc[::-1]
    if train_mode:
        df['type'] = df['type'].map({'拖网': 0, '围网': 1, '刺网': 2})
        label = np.array(df['type'].iloc[0])
        df = df.drop(['type'], axis=1)
    else:
        label = None
    df['dis'] = np.sqrt(df['x'] ** 2 + df['y'] ** 2)# 添加了一个距离特征
    features = np.array([
        df['x'].std(), df['x'].mean(), df['x'].max(), df['x'].min(),
        df['y'].std(), df['y'].mean(), df['y'].max(), df['y'].min(),
        df['速度'].mean(), df['速度'].std(), df['速度'].max(), df['速度'].min(),
        df['方向'].mean(), df['方向'].std(), df['方向'].max(), df['方向'].min(),
        df['dis'].mean(), df['dis'].std(),df['dis'].max(), df['dis'].min(),
                         ])
    return features, label

def load_data2():
    path = './data/hy_round1_train_20200102'
    train_file = os.listdir(path)
    X = []
    Y = []
    for i, each in enumerate(train_file):
        if not i % 1000:  #每读1000个文件输出一次
            print(i)
        each_path = os.path.join(path, each)
        df = pd.read_csv(each_path)
        x, y = get_feature2(df)
        X.append(x)
        Y.append(y)
    X = np.array(X)
    Y = np.array(Y)
    return X, Y

X,Y= load_data2()


# In[18]:


X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=0)

params = {'max_depth':range(5,20),'criterion':np.array(['entropy','gini']),"class_weight":['balanced'],"random_state":[2021]}
clf = GridSearchCV(estimator=tree.DecisionTreeClassifier(), param_grid=params, cv=8, n_jobs=5, scoring="f1_macro")
clf.fit(X_train,y_train)
print("Best set score:{:.2f}".format(clf.best_score_))
print("Best parameters:{}".format(clf.best_params_))
print("Test set score:{:.2f}".format(clf.score(X_test, y_test)))
eval(clf,X_train, X_test, y_train, y_test)

